﻿using System;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Isuka.Excel.Dynamic;
using Isuka.Linq;
using Isuka.Reflection;

namespace Isuka.Excel
{
    internal static class XRowToModelManager
    {
        public static PropertyInfo GetPropertyInfoByIndex(Type type, int rownum)
        {
            Contract.Requires(type != null);

            return
                MemberHelper
                    .GetPropertyInfoDictionary(type)
                    .FindByA<XRowToModelPropertyAttribute>(a => a.Index == rownum)
                    .FirstOrDefault()
                    ;
        }
    }

    /// <summary>
    /// セル範囲をモデルに変換するための基底クラス
    /// </summary>
    public abstract class XRowToModelBase
        : Auxiliaries.NotificationObject
    {
        protected readonly dynamic row;
        protected readonly int rownum;

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="row"></param>
        protected XRowToModelBase(DynamicRow row)
        {
            Contract.Requires(row != null);
            this.row = row.Copy();
            this.rownum = row.CurrentRow;
        }

        /// <summary>
        /// Excel のイベントを受信するコンストラクター
        /// </summary>
        /// <param name="sheet"></param>
        /// <param name="row"></param>
        public XRowToModelBase(XWorksheet sheet, DynamicRow row)
            : this(row)
        {
            Contract.Requires(sheet != null);
            Contract.Requires(row != null);

            sheet.RangeChanged += e =>
            {
                if (e.Row == rownum)
                {
                    var info = XRowToModelManager.GetPropertyInfoByIndex(GetType(), e.Column);
                    if (info != null)
                    {
                        RaisePropertyChanged(info.Name);
                    }
                }
            };
        }

        /// <summary>
        /// Excel セルから値を受信する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyExpression"></param>
        /// <returns></returns>
        protected T GetValue<T>(Expression<Func<T>> propertyExpression)
        {
            Contract.Requires(propertyExpression != null);

            var info = Gate.NotNull(MemberHelper.GetMemberInfo(propertyExpression));

            return info
                    .GetAttribute<XRowToModelPropertyAttribute>()
                    .Select(x =>
                    {
                        object value = row[x.Index];
                        if (value == null)
                        {
                            return default(T);
                        }
                        else
                        {
                            return (T)Convert.ChangeType((object)row[x.Index], typeof(T));
                        }
                    })
                    .First()
                    ;
        }

        /// <summary>
        /// Excel セルに値を設定する
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="propertyExpression"></param>
        /// <param name="value"></param>
        protected void SetValue<T>(Expression<Func<T>> propertyExpression, T value)
        {
            Contract.Requires(propertyExpression != null);

            var info = Gate.NotNull(MemberHelper.GetMemberInfo(propertyExpression));
            info.GetAttribute<XRowToModelPropertyAttribute>().ForEach(x => row[x.Index] = value);
            RaisePropertyChanged(info.Name);
        }
    }
}
